package org.docx4j.model;


import java.math.BigInteger;
import java.util.HashMap;
import java.util.Stack;

import org.docx4j.XmlUtils;
import org.docx4j.jaxb.Context;
import org.docx4j.model.styles.StyleUtil;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.ThemePart;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.openpackaging.parts.WordprocessingML.NumberingDefinitionsPart;
import org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart;
import org.docx4j.wml.BooleanDefaultTrue;
import org.docx4j.wml.CTLanguage;
import org.docx4j.wml.CTTblPrBase;
import org.docx4j.wml.DocDefaults;
import org.docx4j.wml.HpsMeasure;
import org.docx4j.wml.PPr;
import org.docx4j.wml.PPrBase.NumPr.NumId;
import org.docx4j.wml.ParaRPr;
import org.docx4j.wml.RPr;
import org.docx4j.wml.Style;
import org.docx4j.wml.TblPr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class works out the actual set of properties (paragraph or run)
 * which apply, following the order specified in ECMA-376.
 *
 * 该类根据ECMA-376中指定的顺序，计算实际适用的属性集（段落或运行）。
 * From ECMA-376 > Part3 > 2 Introduction to WordprocessingML > 2.8 Styles > 2.8.10 Style Application 
 * at http://www.documentinteropinitiative.org/implnotes/ecma-376/P3-2.8.10.aspx
 *
 * PropertyResolver 是 docx4j 库中的一个核心类，用于处理 Microsoft Word 文档中的样式属性解析。
 * 它根据 ECMA-376 标准定义的规则，计算出应用于文档元素（段落和运行）的最终属性集。
 * (See also Part 4, 2.7.2 which is the normative bit...)

	With the various flavors of styles available, multiple style types can be 
	applied to the same content within a file, which means that properties must 
	be applied in a specific deterministic order. As with inheritance, the 
	resulting formatting properties set by one type can be unchanged, removed, 
	or altered by following types.
	 有了各种风格可供选择，可以有多种风格类型
	 应用于文件中的相同内容，这意味着属性必须
	 以特定的确定性顺序应用。与继承一样
	 由一种类型设置的结果格式属性可以被以下类型更改、删除或更改。

	The following table illustrates the order of application of these defaults, 
	and which properties are impacted by each:
     下表说明了这些默认值的应用顺序，以及每个默认值会影响哪些属性：
	This process can be described as follows: 
	 该过程可以描述如下：
	First, the document defaults are applied to all runs and paragraphs in 
	the document.
   首先，将文档默认属性应用到文档中的所有运行和段落。
	
	Next, the table style properties are applied to each table in the document, 
	following the conditional formatting inclusions and exclusions specified 
	per table.
    接下来，根据为每个表指定的条件格式包含和排除，将表样式属性应用于文档中的每个表。
	
	Next, numbered item and paragraph properties are applied to each paragraph 
	formatted with a numbering style.
    接下来，将编号的项目和段落属性应用于每个段落用编号样式格式化。
	
	Next, paragraph and run properties are 
	applied to each paragraph as defined by the paragraph style. 
    接下来，段落和游程属性将应用于段落样式定义的每个段落。
	Next, run properties are applied to each run with a specific character style 
	applied.
   接下来，将运行块属性应用于用特定字符样式应用的每个运行块。
	
	Finally, we apply direct formatting (paragraph or run properties not from 
	styles).
     最后，我们应用直接格式化（段落或游程属性不是来自样式）。

    总结：
 1. 属性解析顺序该类严格按照 ECMA-376 标准定义的顺序应用属性：
 ·文档默认值：应用于文档中所有段落和运行
 ·表格样式属性：应用于文档中的每个表格
 ·编号项和段落属性：应用于使用编号样式的段落
 ·段落样式属性：应用于每个段落
 ·字符样式属性：应用于具有特定字符样式的每个运行
 ·直接格式化：应用非样式来源的段落或运行属性

 2. 样式继承处理
 通过维护样式层次结构，正确处理样式的继承关系，包括：
 ·basedOn 关系：样式可以基于其他样式
 ·默认样式（如 "Normal" 段落样式和 "DefaultParagraphFont" 字符样式）
 ·链接样式处理

	-----------



	Things which are unclear:
	
	 - the role of w:link on a paragraph style (eg Heading1 links to "Heading1char"),
	   experimentation in Word 2007 suggests the w:link is not used at all
	   
	 - indeed, "Heading1char" is not used at all?

	 不清楚的事情：
	 w在段落风格中的作用：链接（例如Heading1链接到“Heading1char”），
	 Word 2007中的实验表明根本没有使用w:link
	 -事实上，“Heading1char”根本没有使用？
	-----------

	 docx4all does not use this; its org.docx4all.swing.text.StyleSheet
	 uses MutableAttributeSet's resolve function to climb the style hierarchy.
	   
	 This is most relevant to XSLFO, which unlike CSS, doesn't have a concept of 
	 style. HTML NG2 uses CSS inheritance, and so doesn't need it.

 主要内容分析
 docx4all 的处理方式：
 docx4all（另一个处理 docx 的项目）不使用 PropertyResolver
 它使用 org.docx4all.swing.text.StyleSheet 类
 该类通过 MutableAttributeSet 的 resolve 函数来遍历样式层次结构
 XSL-FO 的重要性：
 PropertyResolver 对于 XSL-FO 最为重要
 原因是 XSL-FO 不像 CSS 那样具有样式概念
 XSL-FO 需要显式计算和应用样式属性
 HTML NG2 的处理方式：
 HTML NG2 使用 CSS 继承机制
 因此不需要 PropertyResolver 这样的类
 设计背景
 这段注释解释了为什么需要 PropertyResolver 类：
 XSL-FO 的限制：XSL-FO 是一种页面描述语言，没有像 CSS 那样的内置样式继承机制，因此需要在生成 XSL-FO 之前手动解析和应用样式层次结构
 CSS 的优势：CSS 天然支持样式继承，浏览器会自动处理样式继承和覆盖，因此在 HTML 渲染中不需要额外的样式解析步骤
 不同实现的选择：不同的项目（如 docx4all）可能采用不同的技术方案来处理样式解析
 这说明 PropertyResolver 主要用于需要将 Word 文档样式转换为不具备样式继承机制的输出格式（如 XSL-FO/PDF）的场景

 * @author jharrop
 *
 * 通过维护样式层次结构，正确处理样式的继承关系，包括：
 * basedOn 关系：样式可以基于其他样式
 * 默认样式（如 "Normal" 段落样式和 "DefaultParagraphFont" 字符样式）
 * 链接样式处理
 *
 *
 * 属性解析器
 */
public class PropertyResolver {
	
	private static Logger log = LoggerFactory.getLogger(PropertyResolver.class);
	
//	private DocDefaults docDefaults;
   // 	存储文档默认段落和运行属性
	private PPr documentDefaultPPr;
	private RPr documentDefaultRPr;
	
	public RPr getDocumentDefaultRPr() {
		return documentDefaultRPr;
	}
	public PPr getDocumentDefaultPPr() {
		return documentDefaultPPr;
	}

	private StyleDefinitionsPart styleDefinitionsPart;
	
	private WordprocessingMLPackage wordMLPackage;
	
	/**
	 * All styles in the Style Definitions Part.
	 * 当前文档中所有可用样式的映射
	 *
	 */
	private org.docx4j.wml.Styles styles;

	/**
	 * Map of all styles in the Style Definitions Part.
	 * Note, you need to manually keep this up to date
	 *
	 * 当前文档中所有可用样式的映射
	 * 注意，您需要手动更新此内容
	 */
	private java.util.Map<String, org.docx4j.wml.Style>  liveStyles = null;

	/**
	 * ocx4j 库中用于处理 Word 文档主题部分的核心类，对应于 Word 文档中的 /word/theme/theme1.xml 文件。
	 */
	private ThemePart themePart;
	private NumberingDefinitionsPart numberingDefinitionsPart;


	private java.util.Map<String, PPr>  resolvedStylePPrComponent = new HashMap<String, PPr>();

	/**
	 * This map also contains the rPr component of a pPr
	 */
	private java.util.Map<String, RPr>  resolvedStyleRPrComponent = new HashMap<String, RPr>();
	
	public PropertyResolver(WordprocessingMLPackage wordMLPackage) throws Docx4JException {
		
		this.wordMLPackage = wordMLPackage;
		
		MainDocumentPart mdp = wordMLPackage.getMainDocumentPart();
		
		styleDefinitionsPart = mdp.getStyleDefinitionsPart(true);
		themePart = mdp.getThemePart();
		numberingDefinitionsPart = mdp.getNumberingDefinitionsPart();
		if (wordMLPackage.getMainDocumentPart().getDocumentSettingsPart()!=null
				&& wordMLPackage.getMainDocumentPart().getDocumentSettingsPart().getContents()!=null) {
			themeFontLang = wordMLPackage.getMainDocumentPart().getDocumentSettingsPart().getContents().getThemeFontLang();
		}
		init();		
	}
	
	CTLanguage themeFontLang = null;
	
//	public PropertyResolver(StyleDefinitionsPart styleDefinitionsPart,
//							ThemePart themePart,
//							NumberingDefinitionsPart numberingDefinitionsPart) throws Docx4JException {
//		
//		this.styleDefinitionsPart= styleDefinitionsPart;
//		this.themePart = themePart;
//		this.numberingDefinitionsPart = numberingDefinitionsPart;
//		init();
//	}
	
	String defaultParagraphStyleId;  // "Normal" in English, but ...
	String defaultCharacterStyleId;
	
	private void init() throws Docx4JException {

//		styleDefinitionsPart.createVirtualStylesForDocDefaults();
		
		try {
			defaultParagraphStyleId = this.styleDefinitionsPart.getDefaultParagraphStyle().getStyleId();
		} catch (NullPointerException npe) {
			log.warn("No default paragraph style!!");
		}
		try {
			defaultCharacterStyleId = this.styleDefinitionsPart.getDefaultCharacterStyle().getStyleId();
		} catch (NullPointerException npe) {
			log.warn("No default character style!!");
		}

		// Initialise styles
		styles = (org.docx4j.wml.Styles)styleDefinitionsPart.getJaxbElement();	
		initialiseLiveStyles();		
		
//		Style docDefaults = styleDefinitionsPart.getStyleById("DocDefaults");
		DocDefaults docDefaults = styleDefinitionsPart.getJaxbElement().getDocDefaults();
        if(log.isDebugEnabled()) {
            log.debug(XmlUtils.marshaltoString(docDefaults, true, true));
        }
        //默认的段属性
		documentDefaultPPr = new PPr();
		//默认的运行块属性
		documentDefaultRPr = new RPr();        	

		if (docDefaults!=null
				&& docDefaults.getPPrDefault()!=null
				&& docDefaults.getPPrDefault().getPPr()!=null) {
			
			documentDefaultPPr = docDefaults.getPPrDefault().getPPr();
        }
		
		if (docDefaults!=null
				&& docDefaults.getRPrDefault()!=null
				&& docDefaults.getRPrDefault().getRPr()!=null) {
			
			documentDefaultRPr = docDefaults.getRPrDefault().getRPr();
        }
		
		if (documentDefaultRPr.getSz()==null) {
			// Make Word default explicit
			//该类用于表示半点（half-point）度量单位
			//在Word中，字体大小以半点为单位存储
			//这段代码确保文档具有明确的默认字体大小（10磅），这是Microsoft Word的默认字体大小。
			// 通过显式设置这个默认值，PropertyResolver 类可以确保在解析样式属性时有一致的基础值，避免因缺失默认值而导致的格式问题。
			HpsMeasure sz20 = new HpsMeasure(); 
			sz20.setVal(BigInteger.valueOf(20));
			documentDefaultRPr.setSz(sz20);
		}

		addNormalToResolvedStylePPrComponent();
		addDefaultParagraphFontToResolvedStyleRPrComponent();
	}


	/**
	 * Add the effective properties of Normal style
	 * to resolvedStylePPrComponent.
	 *
	 * defaultParagraphStyleId 的作用
	 * defaultParagraphStyleId 是文档中段落的默认样式ID，通常对应于 "Normal" 样式。它的主要作用包括：
	 * 基础样式：作为所有段落样式的基础，当段落没有明确指定样式时，会使用这个默认样式
	 * 继承起点：在样式继承链中作为根节点，其他样式可能基于
	 */
	private void addNormalToResolvedStylePPrComponent() {
		
		Stack<PPr> pPrStack = new Stack<PPr>();
//		String styleId = "Normal";
		String styleId = defaultParagraphStyleId;
		// 收集该样式及其父样式的属性
		fillPPrStack(styleId, pPrStack);
		pPrStack.push(documentDefaultPPr);
		// 应用所有属性，构建最终的有效属性集
		PPr effectivePPr = factory.createPPr();			
		// Now, apply the properties starting at the top of the stack
		//使用栈结构确保属性应用顺序正确，子样式属性优先级最高
		//按照正确的优先级顺序构建最终的段落属性集。
		//根据之前代码的压栈顺序，属性应用遵循 ECMA-376 标准：
		//首先应用文档默认属性 (documentDefaultPPr)
		//然后递归应用父样式属性
		//最后应用具体样式属性（具有最高优先级）
		while (!pPrStack.empty() ) {
			PPr pPr = pPrStack.pop();
			//applyPPr 方法负责将源属性 (pPr) 合并到目标属性 (effectivePPr) 中
			//新的属性值会覆盖已存在的同类型属性
			//这确保了子样式可以覆盖父样式和默认设置
			applyPPr(pPr, effectivePPr);
		}
		resolvedStylePPrComponent.put(styleId, effectivePPr);		
	}

	/**
	 * 用于计算并存储默认字符样式的有效属性。
	 * 该方法的主要目的是构建默认字符样式 (DefaultParagraphFont) 的有效运行属性 (RPr)，并将其缓存以供后续使用。
	 */
	private void addDefaultParagraphFontToResolvedStyleRPrComponent() {
	Stack<RPr> rPrStack = new Stack<RPr>();
	    //这点本人有些疑惑，难道是默认段中默认字样式优先级别最高吗？
		fillRPrStack(defaultParagraphStyleId, rPrStack);
			// Since default font size might be in there.
		fillRPrStack(defaultCharacterStyleId, rPrStack);
		rPrStack.push(documentDefaultRPr);


			
		RPr effectiveRPr = factory.createRPr();
		// 开始从优先级低 先优先级高的覆盖应用。
		// Now, apply the properties starting at the top of the stack
		while (!rPrStack.empty() ) {
			RPr rPr = rPrStack.pop();
			applyRPr(rPr, effectiveRPr);
		}
		resolvedStyleRPrComponent.put(defaultCharacterStyleId, effectiveRPr);		
	}

	/**
	 * 该方法用于计算表格的有效样式，根据ECMA-376标准处理表格样式继承。
	 * 根据传入的表格属性 tblPr 计算最终的表格样式
	 * 处理样式继承关系，确保正确应用基于关系的样式属性
	 * 返回包含所有有效表格属性的完整样式对象
	 * @param tblPr
	 * @return
	 */
	public Style getEffectiveTableStyle(TblPr tblPr) {
		// OK to pass this a null tblPr.
		
		Stack<Style> tableStyleStack = new Stack<Style>();
		//如果提供了表格属性且包含样式定义，则通过 fillTableStyleStack 方法构建样式继承栈
		//该方法会递归查找基于关系，将所有相关的表格样式压入栈中
		if (tblPr !=null && tblPr.getTblStyle()!=null) {
			String styleId = tblPr.getTblStyle().getVal();
			log.debug("Table style: " + styleId);
			fillTableStyleStack(styleId, tableStyleStack);
		} else {
			log.debug("No table style specified");
		}
		//如果栈不为空，弹出栈顶样式（最高优先级的样式）作为基础
		//如果栈为空，创建一个新的空样式对象
		Style result;
		if (tableStyleStack.size()>0 ) {
			result = XmlUtils.deepCopy(tableStyleStack.pop());
		} else {
			result = Context.getWmlObjectFactory().createStyle();
			CTTblPrBase emptyPr = Context.getWmlObjectFactory().createCTTblPrBase();
			result.setTblPr(emptyPr);
			if (tblPr==null) {
				// Return empty style object
				log.info("Generated empty tblPr" );
				return result;
			}			
		}
		//依次弹出栈中剩余的样式并应用到结果对象
		//由于栈的特性，越靠近栈底的样式（祖先样式）越先应用，符合样式继承的逻辑
		while (!tableStyleStack.empty() ) {
			StyleUtil.apply(tableStyleStack.pop(), result);
		}
		//最后应用传入的直接表格属性，确保其优先级最高
		// Finally apply the tblPr we were passed
		result.setTblPr(StyleUtil.apply(tblPr, result.getTblPr()));
		
		// Sanity check
		if (result.getTblPr()==null) {
			log.error("Null tblPr. FIXME" );
		}
		//返回包含所有表格属性的完整 Style 对象，而不仅仅是 TblPr
		return result;
	}
	
	/**
	 * Ascend the style hierarchy, capturing the table styles
	 *  沿样式层级向上追溯，捕获表格样式
	 * @param stylename
	 * @param effectivePPr
	 */
	private void fillTableStyleStack(String styleId, Stack<Style> tableStyleStack) {
		// get the style
		Style style = liveStyles.get(styleId);
		
		// add it to the stack
		if (style==null) {
			// No such style!
			// For now, just log it..
			log.error("Style definition not found: " + styleId);
			return;
		}
		
		tableStyleStack.push(style);
		log.debug("Added " + styleId + " to table style stack");
		
		// if it is based on, recurse
    	if (style.getBasedOn()==null) {
			log.debug("Style " + styleId + " is a root style.");
    	} else if (style.getBasedOn().getVal()!=null) {
        	String basedOnStyleName = style.getBasedOn().getVal();           	
        	fillTableStyleStack( basedOnStyleName, tableStyleStack);
    	} else {
    		log.debug("No basedOn set for: " + style.getStyleId() );
    	}
		
	}
	
	
	/**
	 * Follow the resolution rules to return the
	 * paragraph properties which actually apply,
	 * given this pPr element (on a w:p).
	 * 
	 * Note 1:  the properties are not the definition
	 * of any style name returned.
	 * Note 2:  run properties are not resolved 
	 * or returned by this method.
	 * 
	 * What is returned is a live object.  If you
	 * want to change it, you should clone it first!
	 *
	 *
	 * 根据解析规则，返回实际应用于当前段落属性（w:p中的pPr元素）的段落属性。
	 * 注1：返回的属性并非任何样式名称的定义。
	 * 注2：段落属性不会被解析或返回，该方法只返回对象。
	 * 返回的是一个动态对象。如需修改，请先克隆副本！
	 *
	 * 此方法通过解析pPr元素（段落属性），结合样式继承规则（如w:pPr/w:pStyle），返回最终生效的段落属性‌
	 *  
	 * @param expressPPr
	 * @return
	 */
	public PPr getEffectivePPr(PPr expressPPr) {
		
		// Used by docx4j-export-fo project.
		
		PPr effectivePPr = null;
		//	First, the document defaults are applied
		
			// Done elsewhere
			// PPr effectivePPr = (PPr)XmlUtils.deepCopy(documentDefaultPPr);
		
		//	Next, the table style properties are applied to each table in the document, 
		//	following the conditional formatting inclusions and exclusions specified 
		//	per table. 
		
			// TODO - if the paragraph is in a table?
				
		//	Next, numbered item and paragraph properties are applied to each paragraph 
		//	formatted with a *numbering *style**.
		
			// TODO - who uses numbering styles (as opposed to numbering
			// via a paragraph style or direct formatting)?
		
		//  Next, paragraph and run properties are 
		//	applied to each paragraph as defined by the paragraph style.
		// 首先，应用文档默认值
		//在其他地方完成 PPr effectivePPr = (PPr)XmlUtils.deepCopy(documentDefaultPPr);
		//接下来，将表格样式属性应用于文档中的每个表格， 遵循为每个表格指定的条件格式包含和排除规则
		// TODO - 如果段落在表格中怎么办？
		// 接下来，将编号项目和段落属性应用于每个使用编号样式格式化的段落。
		//  TODO - 谁使用编号样式（相对于通过段落样式或直接格式化进行编号）？
		//接下来，段落和运行属性将根据段落样式定义应用于每个段落。

		PPr resolvedPPr = null;
		String styleId;
		if (expressPPr == null || expressPPr.getPStyle() == null ) {
//			styleId = "Normal";
			styleId = defaultParagraphStyleId;
			
		} else {
			styleId = expressPPr.getPStyle().getVal();
			if (styleId==null) {
                if(log.isWarnEnabled()) {
                    log.warn("Missing style id: " + XmlUtils.marshaltoString(expressPPr));
                }
				if (log.isDebugEnabled()) {
					Throwable t = new Throwable();
					log.debug("Null styleId produced by code path", t);
				} else {
					log.warn("Enable debug level logging to see code path");					
				}
				styleId = defaultParagraphStyleId;
			}
		}
		resolvedPPr = getEffectivePPr(styleId);
		
				
		//	Next, run properties are applied to each run with a specific character style 
		//	applied. 
		
			// Not for pPr
				
		//	Finally, we apply direct formatting (paragraph or run properties not from 
		//	styles).		
		if (hasDirectPPrFormatting(expressPPr) ) {
			if (resolvedPPr==null) {
				log.warn("resolvedPPr was null. Look into this?");
				effectivePPr = Context.getWmlObjectFactory().createPPr();
			} else {
				effectivePPr = (PPr)XmlUtils.deepCopy(resolvedPPr);
			}
			applyPPr(expressPPr, effectivePPr);
			return effectivePPr;
		} else {
			return resolvedPPr;
		}
		
	}

	/**
	 * Follow the resolution rules to return the
	 * paragraph properties which actually apply,
	 * given this paragraph style
	 * 
	 * Note 1:  the properties are not the definition
	 * of any style name returned.
	 * Note 2:  run properties are not resolved 
	 * or returned by this method.
	 * 
	 * What is returned is a live object.  If you
	 * want to change it, you should clone it first!
	 * 遵循解析规则，返回给定段落样式实际应用的段落属性。
	 *
	 * 注意1：返回的属性不是任何样式名称的定义。
	 * 注意2：运行属性不会被此方法解析或返回。
	 *
	 * 返回的是一个活动对象。如果你想修改它，应该先克隆它。
	 *  
	 * @param expressPPr
	 * @return
	 */
	public PPr getEffectivePPr(String styleId) {
		// 首先检查是否已计算过该样式的结果，如果已存在则直接返回，避免重复计算。
		PPr resolvedPPr = resolvedStylePPrComponent.get(styleId);
		
		if (resolvedPPr!=null) {
			return resolvedPPr;
		}
		// 从当前可用样式映射中查找指定ID的样式，如果找不到则记录错误并返回null。
		// Hmm, have to do the work
		Style s = liveStyles.get(styleId);
		
		if (s==null) {
			log.error("Couldn't find style: " + styleId);
			return null;
		}
		
//		PPr expressPPr = s.getPPr();
//		if (expressPPr==null) {
//			// A paragraph style may have no w:pPr component
//			log.debug("style: " + styleId + " has no PPr");
//			String normalId = this.styleDefinitionsPart.getDefaultParagraphStyle().getStyleId();			
//			resolvedPPr = resolvedStylePPrComponent.get(normalId);
//			return resolvedPPr;
//		}
		
		//  Next, paragraph and run properties are 
		//	applied to each paragraph as defined by the paragraph style.
		Stack<PPr> pPrStack = new Stack<PPr>();
		// Haven't done this one yet				
		fillPPrStack(styleId, pPrStack);
		// Finally, on top
		pPrStack.push(documentDefaultPPr);
						
		resolvedPPr = factory.createPPr();			
		// Now, apply the properties starting at the top of the stack
		while (!pPrStack.empty() ) {
			PPr pPr = pPrStack.pop();
			applyPPr(pPr, resolvedPPr);
		}
		resolvedStylePPrComponent.put(styleId, resolvedPPr);
		return resolvedPPr;
	}
	
	/**
	 * 计算运行(run)元素的最终有效属性
	 * 该方法是 PropertyResolver 类中的一个关键方法，
	 * 用于计算文本运行(run)元素的最终有效属性(RPr)。它考虑了段落样式和直接格式化的影响，
	 * 主要用于PDF通过XSL-FO导出的场景。
	 * @param expressRPr 直接应用于运行(run)元素的属性
	 * @param pPr - 包含段落属性的PPr对象
	 * @return
	 */
	public RPr getEffectiveRPr(RPr expressRPr, PPr pPr) {
		
		// NB Currently used in PDF viaXSLFO only
		
		if (pPr==null) {
			log.debug("pPr was null");
		} else {
			// At the pPr level, what rPr do we have?
			// .. ascend the paragraph style tree
			if (pPr.getPStyle()==null) {
				if (log.isDebugEnabled()) {
					log.debug("No pstyle:");
					log.debug(XmlUtils.marshaltoString(pPr, true, true));
				}
			} else {
				if (log.isDebugEnabled()) {
					log.debug("pstyle:" + pPr.getPStyle().getVal());
				}
				//获取该段落样式对应的run属性
				RPr pPrLevelRunStyle = getEffectiveRPr(pPr.getPStyle().getVal());
				// .. and apply those
				
				if (log.isDebugEnabled()) {
					log.debug("Resulting pPrLevelRunStyle: " + XmlUtils.marshaltoString(pPrLevelRunStyle));
				}
				//合并段落样式中的run属性和直接run属性
				return getEffectiveRPrUsingPStyleRPr(expressRPr, pPrLevelRunStyle);
			}
			// Check Paragraph rPr (our special hack of using ParaRPr to format a fo:block)
			// 2013 10 02: doubts whether this is right?
			if ((expressRPr == null) && (pPr.getRPr() != null) && (hasDirectRPrFormatting(pPr.getRPr())) ) {
				log.debug("No express rPr, using p level");
				return getEffectiveRPrUsingPStyleRPr(expressRPr, 
						StyleUtil.apply(pPr.getRPr(), Context.getWmlObjectFactory().createRPr()));
			} 
			log.debug("pPr not null, but falling through");
			
		}

		return getEffectiveRPrUsingPStyleRPr( expressRPr, null);
		
	}

	/**
	 * Return effective rPr, as follows: Starting with the rPr from the pStyle, 
	 * apply character style (if any) specified on the run,
	 * then any other direct (ad-hoc) run formatting.
	 * 
	 * @param expressRPr
	 * @param rPrFromPStyle should be rPr from the paragraph style (as opposed to rPr in the direct pPr, which is only relevant to the paragraph mark)
	 * @return
	 */
	public RPr getEffectiveRPrUsingPStyleRPr(RPr expressRPr, RPr rPrFromPStyle) {
		
		// Note, RPr pPrLevelRunStyle should be rPr from the paragraph style
		// (as opposed to rPr in the direct pPr, which is only
		//  relevant to the paragraph mark)
		
		log.debug("in getEffectiveRPrUsingPStyle");
//		Throwable t = new Throwable();
//		t.printStackTrace();
		
		
//		Idea is that you pass pPr if you are using this for XSL FO,
//		since we need to take account of rPr in paragraph styles
//		(but not the rPr in a pPr direct formatting, since
//       that only applies to the paragraph mark). 
//		 * For HTML/CSS, this would be null (since the pPr level rPr 
//		 * is made into a separate style applied via a second value in
//		 * the class attribute).  But, in the CSS case, this
		// function is not used - since the rPr is made into a style as well.
		

		//	First, the document defaults are applied		
		RPr effectiveRPr = null;
		if (documentDefaultRPr==null) {
			log.warn("documentDefaultRPr null");
			effectiveRPr = Context.getWmlObjectFactory().createRPr();
		} else {		
			effectiveRPr = (RPr)XmlUtils.deepCopy(documentDefaultRPr);
		}
//			
//			// Apply DefaultParagraphFont.  We only do it explicitly
//			// here as per conditions, because if there is a run style,
//			// walking the hierarchy will include this if it is needed
//			if (expressRPr == null || expressRPr.getRStyle() == null ) {
//				applyRPr(resolvedStyleRPrComponent.get(defaultCharacterStyleId), effectiveRPr);								
//			}
		
		if (rPrFromPStyle!=null) {
			effectiveRPr=StyleUtil.apply(rPrFromPStyle, effectiveRPr);
		}		
		
		
//    	System.out.println("start\n" + XmlUtils.marshaltoString(effectiveRPr, true, true));
		
		
		//	Next, the table style properties are applied to each table in the document, 
		//	following the conditional formatting inclusions and exclusions specified 
		//	per table. 
		
			// TODO - if the paragraph is in a table?
				
		//	Next, numbered item and paragraph properties are applied to each paragraph 
		//	formatted with a *numbering *style**.
		
//			 TODO - who uses numbering styles (as opposed to numbering
			// via a paragraph style or direct formatting)?
		
		//  Next, paragraph and run properties are 
		//	applied to each paragraph as defined by the paragraph style
		// (this includes run properties defined in a paragraph style,
		//  but not run properties directly included in a pPr in the
		//  document (those only apply to a paragraph mark).
//		applyRPr(pPrLevelRunStyle, effectiveRPr);
					
		//	Next, run properties are applied to each run with a specific character style 
		//	applied. 		
		RPr resolvedRPr = null;
		String runStyleId;
		if (expressRPr != null && expressRPr.getRStyle() != null ) {
			runStyleId = expressRPr.getRStyle().getVal();
			
			// we want to ignore doc default contrib, if rFonts or sz is set in the pStyle
			boolean rFontsMissing = effectiveRPr.getRFonts()==null;
			boolean szMissing = effectiveRPr.getSz()==null;
			boolean langMissing = effectiveRPr.getLang()==null;
			resolvedRPr = getEffectiveRPr(runStyleId, rFontsMissing, szMissing, langMissing); 
			StyleUtil.apply(resolvedRPr, effectiveRPr);
//	    	System.out.println("resolved=\n" + XmlUtils.marshaltoString(resolvedRPr, true, true));
			
		}
//    	System.out.println("effective is now\n" + XmlUtils.marshaltoString(effectiveRPr, true, true));
				
		//	Finally, we apply direct formatting (run properties not from 
		//	styles).		
		if (hasDirectRPrFormatting(expressRPr) ) {			
			//effectiveRPr = (RPr)XmlUtils.deepCopy(effectiveRPr);			
//	    	System.out.println("applying express\n" + XmlUtils.marshaltoString(expressRPr, true, true));
			StyleUtil.apply(expressRPr, effectiveRPr);
		} 
		return effectiveRPr;
		
	}


	/**
	 * apply the rPr in the stack of styles, including documentDefaultRPr
	 * 
	 * @param styleId
	 * @return
	 */
	public RPr getEffectiveRPr(String styleId) {

		return getEffectiveRPr(styleId, true, true, true); 
	}
	/**
	 * apply the rPr in the stack of styles, optionally including documentDefaultRPr
	 * 
	 * @param styleId
	 * @return
	 * @since 8.2.4
	 */
	public RPr getEffectiveRPr(String styleId, 
			boolean applyDocDefaultsRFonts, boolean applyDocDefaultsSz,
			boolean applyDocDefaultsLang) {
		// styleId passed in could be a run style
		// or a *paragraph* style
		
		RPr resolvedRPr = resolvedStyleRPrComponent.get(styleId);
		
		if (resolvedRPr!=null) {
			return resolvedRPr;
		}
		
		// Hmm, have to do the work
		Style s = liveStyles.get(styleId);
		
		if (s==null) {			
			log.error("Couldn't find style: " + styleId);
			log.debug("Couldn't find style: " + styleId, new Throwable());
			return null;
		}

		// Comment out - this style might not have rPr,
		// but an ancestor might!
		
//		RPr expressRPr = s.getRPr();
//		if (expressRPr==null) {
//			log.error("style: " + runStyleId + " has no RPr");
//			resolvedRPr = resolvedStyleRPrComponent.get(defaultCharacterStyleId);
//			return resolvedRPr;
//		}

		
		//	Next, run properties are applied to each run with a specific character style applied. 		
		Stack<RPr> rPrStack = new Stack<RPr>();
		fillRPrStack(styleId, rPrStack);
		
		// Finally, on top
		// The intent is to be able to ignore doc default contrib, 
		// where rFonts or sz is set in the pStyle
		RPr defaultRPr = new RPr();
		if (applyDocDefaultsRFonts) {
			defaultRPr.setRFonts(this.documentDefaultRPr.getRFonts());
		}
		if (applyDocDefaultsSz) {
			defaultRPr.setSz(this.documentDefaultRPr.getSz());
			defaultRPr.setSzCs(this.documentDefaultRPr.getSzCs());
		}
		if (applyDocDefaultsLang) {
			defaultRPr.setLang(this.documentDefaultRPr.getLang());
		}
		rPrStack.push(defaultRPr); 
						
		resolvedRPr = factory.createRPr();			
		// Now, apply the properties starting at the top of the stack
		while (!rPrStack.empty() ) {
			RPr rPr = rPrStack.pop();
            if(log.isDebugEnabled()) {
                log.debug("applying " + XmlUtils.marshaltoString(rPr));
            }
			applyRPr(rPr, resolvedRPr);
		}
		resolvedStyleRPrComponent.put(styleId, resolvedRPr);
		return resolvedRPr;
	}
	
	org.docx4j.wml.ObjectFactory factory = new org.docx4j.wml.ObjectFactory();
	
	private BooleanDefaultTrue newBooleanDefaultTrue(boolean val) {
		
		BooleanDefaultTrue newBooleanDefaultTrue = factory.createBooleanDefaultTrue();
		newBooleanDefaultTrue.setVal(Boolean.valueOf(val));
		return newBooleanDefaultTrue;
	}
	
	private boolean hasDirectPPrFormatting(PPr pPrToApply) {
		
		// NB, any rPr is intentionally ignored,
		// since pPr/rPr is not applicable to anything
		// except the paragraph mark
		
		if (pPrToApply==null) {
			return false;
		}
		
		// Here is where we do the real work.  
		// There are a lot of paragraph properties
		// The below list is taken directly from PPrBase.
		
		//PPrBase.PStyle pStyle;
		
			// Ignore

		if (pPrToApply.getBidi()!=null) {
			return true;		
		}
		
		
		//BooleanDefaultTrue keepNext;
		if (pPrToApply.getKeepNext()!=null) {
			return true;		
		}
		
	
		//BooleanDefaultTrue keepLines;
		if (pPrToApply.getKeepLines()!=null) {
			return true;		
		}
	
		//BooleanDefaultTrue pageBreakBefore;
		if (pPrToApply.getPageBreakBefore()!=null) {
			return true;		
		}
	
		//CTFramePr framePr;
		//BooleanDefaultTrue widowControl;
		if (pPrToApply.getWidowControl()!=null) {
			return true;		
		}
	
		//PPrBase.NumPr numPr;
		//NumPr numPr;
		if (pPrToApply.getNumPr()!=null) {
			return true;		
		}
	
		//BooleanDefaultTrue suppressLineNumbers;
		if (pPrToApply.getSuppressLineNumbers()!=null) {
			return true;		
		}
		
		// PBdr pBdr;
		if (pPrToApply.getPBdr()!=null) {
			return true;		
		}
	
		//CTShd shd;
		if (pPrToApply.getShd()!=null) {
			return true;		
		}
			
		//Tabs tabs;
		if (pPrToApply.getTabs()!=null) {
			return true;		
		}
	
		//BooleanDefaultTrue suppressAutoHyphens;
		//BooleanDefaultTrue kinsoku;
		//BooleanDefaultTrue wordWrap;
		//BooleanDefaultTrue overflowPunct;
		//BooleanDefaultTrue topLinePunct;
		//BooleanDefaultTrue autoSpaceDE;
		//BooleanDefaultTrue autoSpaceDN;
		//BooleanDefaultTrue bidi;
		//BooleanDefaultTrue adjustRightInd;
		//BooleanDefaultTrue snapToGrid;
		//PPrBase.Spacing spacing;
		if (pPrToApply.getSpacing()!=null) {
			return true;		
		}
	
		//PPrBase.Ind ind;
		if (pPrToApply.getInd()!=null) {
			return true;		
		}
	
		//BooleanDefaultTrue contextualSpacing;
		//BooleanDefaultTrue mirrorIndents;
		//BooleanDefaultTrue suppressOverlap;
		//Jc jc;
		if (pPrToApply.getJc()!=null) {
			return true;		
		}
	
		//TextDirection textDirection;
		//PPrBase.TextAlignment textAlignment;
		if (pPrToApply.getTextAlignment()!=null ) {
			return true;		
		}
	
		//CTTextboxTightWrap textboxTightWrap;
		//PPrBase.OutlineLvl outlineLvl;
		if (pPrToApply.getOutlineLvl()!=null ) {
			return true;		
		}
		//PPrBase.DivId divId;
		//CTCnf cnfStyle;
		return false;
		
	}
	
	
	protected void applyPPr(PPr pPrToApply, PPr effectivePPr) {
        if(log.isDebugEnabled()) {
            log.debug("apply " + XmlUtils.marshaltoString(pPrToApply, true, true)
                    + "\n\r to " + XmlUtils.marshaltoString(effectivePPr, true, true));
        }
		
		StyleUtil.apply(pPrToApply, effectivePPr, this.numberingDefinitionsPart);
		
//		if (pPrToApply==null) {
//			return;
//		}
//		
//    	List<Property> properties = PropertyFactory.createProperties(wordMLPackage, pPrToApply); 
//    	for( Property p :  properties ) {
//			if (p!=null) {
////				log.debug("applying pPr " + p.getClass().getName() );
//				((AbstractParagraphProperty)p).set(effectivePPr);  // NB, this new method does not copy. TODO?
//			}
//    	}

        if(log.isDebugEnabled()) {
            log.debug("result " + XmlUtils.marshaltoString(effectivePPr, true, true));
        }
    	
	}
	
	public boolean hasDirectRPrFormatting(RPr rPrToApply) {
		
		if (rPrToApply==null) {
			return false;
		}
		
		// Here is where we do the real work.  
		// There are a lot of run properties
		// The below list is taken directly from RPr, and so
		// is comprehensive.
		
		//RStyle rStyle;
		//RFonts rFonts;
		if (rPrToApply.getRFonts()!=null ) {
			return true;
		}

		//BooleanDefaultTrue b;
		if (rPrToApply.getB()!=null) {
			return true;			
		}

		//BooleanDefaultTrue bCs;
		//BooleanDefaultTrue i;
		if (rPrToApply.getI()!=null) {
			return true;			
		}

		//BooleanDefaultTrue iCs;
		//BooleanDefaultTrue caps;
		if (rPrToApply.getCaps()!=null) {
			return true;			
		}

		//BooleanDefaultTrue smallCaps;
		if (rPrToApply.getSmallCaps()!=null) {
			return true;			
		}

		//BooleanDefaultTrue strike;
		if (rPrToApply.getStrike()!=null) {
			return true;			
		}
		//BooleanDefaultTrue dstrike;
		//BooleanDefaultTrue outline;
		//BooleanDefaultTrue shadow;
		//BooleanDefaultTrue emboss;
		//BooleanDefaultTrue imprint;
		//BooleanDefaultTrue noProof;
		//BooleanDefaultTrue snapToGrid;
		//BooleanDefaultTrue vanish;
		//BooleanDefaultTrue webHidden;
		//Color color;
		if (rPrToApply.getColor()!=null ) {
			return true;			
		}

		//CTSignedTwipsMeasure spacing;
		if (rPrToApply.getSpacing()!=null ) {
			return true;			
		}
		//CTTextScale w;
		//HpsMeasure kern;
		//CTSignedHpsMeasure position;
		//HpsMeasure sz;
		if (rPrToApply.getSz()!=null ) {
			return true;			
		}

		//HpsMeasure szCs;
		//Highlight highlight;
		if (rPrToApply.getHighlight()!=null ) {
			return true;			
		}
		//U u;
		if (rPrToApply.getU()!=null ) {
			return true;			
		}

		//CTTextEffect effect;
		//CTBorder bdr;
		if (rPrToApply.getBdr()!=null ) {
			return true;			
		}
		//CTShd shd;
		if (rPrToApply.getShd()!=null ) {
			return true;			
		}
		//CTFitText fitText;
		//CTVerticalAlignRun vertAlign;
		if (rPrToApply.getVertAlign()!=null ) {
			return true;			
		}
		//BooleanDefaultTrue rtl;
		//BooleanDefaultTrue cs;
		//CTEm em;
		//CTLanguage lang;
		if (rPrToApply.getLang()!=null ) {
			return true;			
		}
		//CTEastAsianLayout eastAsianLayout;
		//BooleanDefaultTrue specVanish;
		//BooleanDefaultTrue oMath;
		//CTRPrChange rPrChange;
		
		// If we got here...
		return false;
	}
	
	private boolean hasDirectRPrFormatting(ParaRPr rPrToApply) {
		
		if (rPrToApply==null) {
			return false;
		}
		
		// Here is where we do the real work.  
		// There are a lot of run properties
		// The below list is taken directly from RPr, and so
		// is comprehensive.
		
		//RStyle rStyle;
		//RFonts rFonts;
		if (rPrToApply.getRFonts()!=null ) {
			return true;
		}

		//BooleanDefaultTrue b;
		if (rPrToApply.getB()!=null) {
			return true;			
		}

		//BooleanDefaultTrue bCs;
		//BooleanDefaultTrue i;
		if (rPrToApply.getI()!=null) {
			return true;			
		}

		//BooleanDefaultTrue iCs;
		//BooleanDefaultTrue caps;
		if (rPrToApply.getCaps()!=null) {
			return true;			
		}

		//BooleanDefaultTrue smallCaps;
		if (rPrToApply.getSmallCaps()!=null) {
			return true;			
		}

		//BooleanDefaultTrue strike;
		if (rPrToApply.getStrike()!=null) {
			return true;			
		}
		//BooleanDefaultTrue dstrike;
		//BooleanDefaultTrue outline;
		//BooleanDefaultTrue shadow;
		//BooleanDefaultTrue emboss;
		//BooleanDefaultTrue imprint;
		//BooleanDefaultTrue noProof;
		//BooleanDefaultTrue snapToGrid;
		//BooleanDefaultTrue vanish;
		//BooleanDefaultTrue webHidden;
		//Color color;
		if (rPrToApply.getColor()!=null ) {
			return true;			
		}

		//CTSignedTwipsMeasure spacing;
		//CTTextScale w;
		//HpsMeasure kern;
		//CTSignedHpsMeasure position;
		//HpsMeasure sz;
		if (rPrToApply.getSz()!=null ) {
			return true;			
		}

		//HpsMeasure szCs;
		//Highlight highlight;
		//U u;
		if (rPrToApply.getU()!=null ) {
			return true;			
		}

		//CTTextEffect effect;
		//CTBorder bdr;
		//CTShd shd;
		//CTFitText fitText;
		//CTVerticalAlignRun vertAlign;
		//BooleanDefaultTrue rtl;
		//BooleanDefaultTrue cs;
		//CTEm em;
		//CTLanguage lang;
		//CTEastAsianLayout eastAsianLayout;
		//BooleanDefaultTrue specVanish;
		//BooleanDefaultTrue oMath;
		//CTRPrChange rPrChange;
		
		// If we got here...
		return false;
	}
	
	protected void applyRPr(RPr rPrToApply, RPr effectiveRPr) {
		
		if (rPrToApply==null) {
			return;
		}
		
		StyleUtil.apply(rPrToApply, effectiveRPr);
		
//    	List<Property> properties = PropertyFactory.createProperties(null, rPrToApply); // wmlPackage null
//    	
//    	for( Property p :  properties ) {
//			if (p!=null) {    		
//				((AbstractRunProperty)p).set(effectiveRPr);  // NB, this new method does not copy. TODO?
//			}
//    	}
		
	}	
	
	protected void applyRPr(ParaRPr rPrToApply, RPr effectiveRPr) {
		
		if (rPrToApply==null) {
			return;
		}

		StyleUtil.apply(rPrToApply, effectiveRPr);
		
//    	List<Property> properties = PropertyFactory.createProperties(null, rPrToApply); // wmlPackage null
//    	
//    	for( Property p :  properties ) {
//			if (p!=null) {    		
//				((AbstractRunProperty)p).set(effectiveRPr);  // NB, this new method does not copy. TODO?
//			}
//    	}
	}	
	
    private static final String HEADING_STYLE = "Heading";
	
    /*
     * @since 3.0.2
     */
    public int getLvlFromHeadingStyle(String style){
    	// Note that this is done using the style ID, not its OutlineLevel,
    	// since Word does it purely on the name of the style!
        int level = -1;
        try{
            level = Integer.parseInt(style.substring(HEADING_STYLE.length(), style.length()).trim());
        } catch (NumberFormatException ex){
            //log.debug(style + " - what level is this? ");
        }

        return level;
    }	
	
	/**
	 * Ascend (recursively) the style hierarchy, capturing the pPr bit.
	 * 递归遍历样式层级，捕获pPr属性。
	 * Doesn't use StyleTree.
	 * 该方法的主要作用是沿着样式继承链向上遍历，
	 * 将所有相关的段落属性(PPr)收集到一个栈中，
	 * 以便后续按正确的优先级顺序应用这些属性
	 *
	 *
	 * 具体：
	 *   栈中元素的正确顺序
	 * 压栈顺序（从空栈开始）：
	 * 首先压入具体样式（子样式）的属性
	 * 递归压入其父样式属性
	 * 最后压入文档默认属性
	 * 最终栈结构：
	 *    栈顶(top)    -> 文档默认属性
	 *                 -> 父样式属性
	 *                 -> 父样式的父样式属性
	 *                 -> ...
	 *    栈底(bottom) -> 具体样式属性（子样式）
	 *    通过 pPrStack.pop() 从栈顶开始取出元素
	 *  最后压入的元素（子样式属性）在栈底，最后被应用，优先级最高
	 *   最先压入的元素（文档默认属性）在栈顶，最先被应用，优先级最低
	 *  子样式在栈底，父样式在栈顶附近。这种设计确保了样式继承的正确性：子样式可以覆盖父样式和默认属性。
	 *  
	 * @param stylename
	 * @param effectivePPr
	 */
	private void fillPPrStack(String styleId, Stack<PPr> pPrStack) {
		// The return value is the style on which styleId is based.
		//返回值是styleId所基于的样式。
		// It is purely for the purposes of ascertainNumId (? no, its used by getEffectivePPr(styleId))
		//它纯粹是为了确定NumId（不，它被getEffectivePPr（styleId）使用）
		//首先检查样式ID是否为空，如果为空则记录日志并返回。
		if (styleId==null) {
			if (log.isDebugEnabled()) {
				Throwable t = new Throwable();
				log.debug("Null styleId produced by code path", t);
			} else {
				log.warn("Null styleId; Enable debug level logging to see code path");					
			}
			return;
		}
		
		// get the style
		//从当前活动样式映射中获取指定ID的样式对象。
		Style style = liveStyles.get(styleId);
		
		// add it to the stack
		if (style==null) {
			// No such style!
			// For now, just log it..
			// 特殊处理"DocDefaults"样式
			if (styleId!=null
					&& styleId.equals("DocDefaults")) {
				// 不需要处理，因为文档默认值已单独处理
				// Don't worry about this.
				// SDP.createVirtualStylesForDocDefaults()
				// creates a DocDefaults style, and makes Normal based on it
				// (and so if a different approach to handling
				//  DocDefaults ... we really should do it one
				//  way consistently).
				// The problem here is, that is typically done
				// after the PropertyResolver is created,
				// so as far as this PropertyResolver is 
				// concerned, the style doesn't exist.
				// And we don't really want to always
				// do createVirtualStylesForDocDefaults() before
				// or during init of PropertyResolver, since that
				// mean any docx saved would contain those
				// virtual styles.
				// Anyway, we don't need to worry about it
				// here, because the doc defaults are still handled...
				//别担心这个。SDP.createVirtualStylesForDocDefaults（）创建了一个DocDefaults样式，
				// 并在此基础上创建了Normal（因此，如果采用不同的方法处理DocDefaults……我们真的应该始终如一地采用一种方法）。
				// 这里的问题是，这通常是在创建PropertyResolver之后完成的，
				// 因此就这个PropertyResolver而言，样式不存在。
				// 我们并不希望在PropertyResolver初始化之前或期间总是执行createVirtualStylesForDocDefaults（）
				// ，因为这意味着保存的任何docx都将包含这些虚拟样式。
				// 不管怎样，我们在这里不需要担心，因为文档默认值仍在处理中。
			} else {
				log.error("Style definition not found: " + styleId);
			}
			return;
		}
		
		// For heading styles, check the outline level is as expected
		//对于标题样式，请检查大纲级别是否符合预期
		if (styleId.startsWith(HEADING_STYLE)) {
			//   从样式ID（如"Heading1"、"Heading2"等）中提取数字部分，作为预期的大纲级别。
			int level = getLvlFromHeadingStyle(styleId);
			//检查是否需要修正
			if (level>0
					&& style.getPPr()!=null
					&& style.getPPr().getOutlineLvl()!=null
					&& style.getPPr().getOutlineLvl().getVal()!=null
					&& style.getPPr().getOutlineLvl().getVal().intValue()!=(level-1)) {
				
				// must use the outline level appropriate to this heading!
				// No need to clone, since Microsoft Word automatically overwrites like this!
				//必须使用适合此标题的大纲级别！无需克隆，因为Microsoft Word会自动覆盖！
				//将大纲级别设置为与标题级别相对应的值（Word中大纲级别从0开始，标题级别从1开始，所以需要减1）。
				//举例说明
				//对于"Heading1"样式：
				//level = 1
				//应该设置的大纲级别 = 0（1-1）
				//对于"Heading3"样式：
				//level = 3
				//应该设置的大纲级别 = 2（3-1）
				//为什么需要这个逻辑？
				//这是为了确保Word文档中的标题样式与其在大纲视图中的层级正确对应，保证文档结构的正确性。Word依赖这个大纲级别来进行导航、生成目录等功能。
				log.info(styleId + " - reset actual outline level with " + (level-1));
				style.getPPr().getOutlineLvl().setVal(BigInteger.valueOf(level-1));
			} 
			// 将当前p样式属性压入栈
			pPrStack.push(style.getPPr());				
		} else {
			// 将当前p样式属性压入栈
			pPrStack.push(style.getPPr());
		}
		
		log.debug("Added " + styleId + " to pPr stack");
		
		// Some styles contain numPr, without specifying
		// their numId!  In this case you have to get it
		// from the numPr in their basedOn style.
		// To save numbering emulator from having to do
		// that work, we make the numId explicit here.
		// 某些样式包含numPr，但未指定其numId！在这种情况下，
		// 您必须从numPr的basedOn样式中获取它。
		// 为了使编号模拟器不必做这项工作，我们在这里显式显示numId。
		boolean ascertainNumId = false;
		if (style.getPPr()!=null 
				&& style.getPPr().getNumPr()!=null
				&& style.getPPr().getNumPr().getNumId()==null) {

			ascertainNumId = true;			
			log.debug(styleId +" ascertainNumId: " + ascertainNumId);
		} else {
			log.debug(styleId +" ascertainNumId: " + ascertainNumId);			
		}
		
		// if it is based on, recurse
    	if (style.getBasedOn()==null) {
			log.debug("Style " + styleId + " is a root style.");
    	} else if (style.getBasedOn().getVal()!=null) {
			// 递归调用确保了整个继承链都被处理
        	String basedOnStyleName = style.getBasedOn().getVal();           	
			log.debug("Style " + styleId + " is based on " + basedOnStyleName);
        	fillPPrStack( basedOnStyleName, pPrStack);

        	Style basedOnStyle = liveStyles.get(basedOnStyleName);
        	if (ascertainNumId && basedOnStyle!=null) {
				// 每一层都会执行相同的逻辑，检查是否需要获取 numId
				//如果中间某层样式缺少 numId，它会从其父样式获取
				//获取后，下一层样式就可以从该层获取 numId
				//这样形成了逐层传递的机制
				//    样式C (缺少numId)
				//     -> 基于样式B (缺少numId)
				//        -> 基于样式A (有完整的numId=5)
				//
				//   处理顺序：
				//   1. 处理样式C，发现缺少numId，递归处理样式B
				//   2. 处理样式B，发现缺少numId，递归处理样式A
				//   3. 处理样式A，发现有完整numId=5
				//   4. 返回到样式B，从样式A获取numId=5并注入
				//   5. 返回到样式C，从样式B获取numId=5并注入
				//
        		// This works via recursion        		
        		//log.debug( XmlUtils.marshaltoString(basedOnStyle, true, true));
        		if (basedOnStyle.getPPr()!=null 
        				&& basedOnStyle.getPPr().getNumPr()!=null
        				&& basedOnStyle.getPPr().getNumPr().getNumId()!=null) {
        			NumId numId = basedOnStyle.getPPr().getNumPr().getNumId();
        			// Attach it at this level - for this to work,
        			// you can't have a style in the basedOn hierarchy
        			// which doesn't have a numPr element, because
        			// in that case there is nowhere to hang the style
					// 在此级别附加它-为了使其工作，basedOn层次结构中不能有没有numPr元素的样式，因为在这种情况下，没有地方挂起样式

        			style.getPPr().getNumPr().setNumId(numId);
        			log.info("Injected numId " + numId);
        		}
        	}
    	} else {
    		log.debug("No basedOn set for: " + style.getStyleId() );
    	}
		
	}

	/**
	 * Ascend the style hierarchy, capturing the rPr bit
	 *  
	 * @param stylename
	 * @param effectivePPr
	 */
	private void fillRPrStack(String styleId, Stack<RPr> rPrStack) {
		
		// get the style
		Style style = liveStyles.get(styleId);
		
		// add it to the stack
		if (style==null) {
			// No such style!
			// For now, just log it..
			log.error("Style definition not found: " + styleId);
			return;
		}
		rPrStack.push(style.getRPr());
		log.debug("Added " + styleId + " to rPr stack");
		
		// if it is based on, recurse
    	if (style.getBasedOn()==null) {
			log.debug("Style " + styleId + " is a root style.");
    	} else if (style.getBasedOn().getVal()!=null) {
        	String basedOnStyleName = style.getBasedOn().getVal();
//        	if (styleId.equals(basedOnStyleName)) {
//    		log.error(XmlUtils.marshaltoString(style));
//    		throw new RuntimeException(styleId + " is basedOn itself!");
//    	} 
        	fillRPrStack( basedOnStyleName, rPrStack);
    	} else {
    		log.debug("No basedOn set for: " + style.getStyleId() );
    	}
		
	}
	
	
    private void initialiseLiveStyles() {
    	
    	log.debug("initialiseLiveStyles()");
		liveStyles = new java.util.HashMap<String, org.docx4j.wml.Style>();
		
		for ( org.docx4j.wml.Style s : styles.getStyle() ) {				
			liveStyles.put(s.getStyleId(), s);	
			log.debug("live style: " + s.getStyleId() );
		}
    	
    }

	
    public boolean activateStyle( String styleId  ) {
    	
    	if (liveStyles.get(styleId)!=null) {
    		// Its already live - nothing to do
    		return true;    		
    	}
    	// Assumption here is that it doesn't exist in your styles part, so..
    	java.util.Map<String, org.docx4j.wml.Style> knownStyles 
    		= StyleDefinitionsPart.getKnownStyles(); // NB KnownStyles.xml, not those in docx!
    	
    	org.docx4j.wml.Style s = knownStyles.get(styleId);
    	
    	if (s==null) {
    		log.error("Unknown style: " + styleId);
    		return false;
    	}
    	    	
    	return activateStyle(s, false); 
    		// false -> don't replace an existing live style with a template
    	
    }
    
    public boolean activateStyle(org.docx4j.wml.Style s) {

    	return activateStyle(s, true);
    	
    }

    private boolean activateStyle(org.docx4j.wml.Style s, boolean replace) {
    	
    	if (liveStyles.get(s.getStyleId())!=null) {
    		// Its already live
    		
    		if (!replace) {    			
    			return false;
    		}
    		
    		// Remove existing entry
			styles.getStyle().remove( 
					liveStyles.get(s.getStyleId()) );				
    	}
    	
    	// Add it
    	// .. to the JAXB object
    	styles.getStyle().add(s);
    	// .. here
    	liveStyles.put(s.getStyleId(), s);
    	
    	// Now, recursively check that what it is based on is present
    	boolean result1;
    	if (s.getBasedOn()!=null) {
    		String basedOn = s.getBasedOn().getVal();
    		result1 = activateStyle( basedOn );
    		
    	} else if ( s.getStyleId().equals(defaultParagraphStyleId)
    			|| s.getStyleId().equals(defaultCharacterStyleId) )
    	{
    		// stop condition
    		result1 = true;
    	} else {
    		
    		log.debug( s.getStyleId() + "  not w:basedOn anything, but that's ok");
    		result1 = true;
    	}
    	
    	// Also add the linked style, if any
    	// .. Word might expect it to be there
    	boolean result2 = true;
    	if (s.getLink()!=null) {
    		
    		org.docx4j.wml.Style.Link link = s.getLink();
    		result2 = activateStyle(link.getVal());
    		
    	}
    	
    	return (result1 & result2);
    	    	
    }
    
    public org.docx4j.wml.Style getStyle(String styleId) {
    	
    	return liveStyles.get(styleId);
    }
	
//		/*
//		a paragraph style does not inherit anything from its linked character style.
//
//		A linked character style seems to be just a Word 2007 user interface
//		hint.  ie if you select some characters in a paragraph and select to
//		apply "Heading 1", what you are actually doing is applying "Heading 1
//		char".  This is determined by looking at the definition of the
//		"Heading 1" style to see what its linked style is.
//		
//		(Interestingly, in Word 2007, if you right click to modify something 
//		 which is Heading 1 char, it modifies both the Heading 1 style and the
//		 Heading 1 char style!.  Haven't looked to see what happens to Heading 1 char
//		 style if you right click to modify a Heading 1 par.)
//
//		 The algorithm Word 2007 seems to use is:
//		    look at the specified style:
//		        1 does it have its own rPr which contains rFonts?
//		        2 if not, what does this styles basedOn style say? (Ignore
//		any linked char style)
//				3 look at styles/rPrDefault 
//				3.1 if there is an rFonts element, do what it says (it may refer you to the theme part, 
//				    in which case if there is no theme part, default to "internally stored settings"
//					(there is no normal.dot; see http://support.microsoft.com/kb/924460/en-us ) 
//					in this case Calibri and Cambria)
//				3.2 if there is no rFonts element, default to Times New Roman.
//		

    //        // 1 does it have its own rPr which contains rFonts?
//    	org.docx4j.wml.RPr rPr = style.getRPr();
//    	if (rPr!=null && rPr.getRFonts()!=null) {
//    		if (rPr.getRFonts().getAscii()!=null) {
//        		return rPr.getRFonts().getAscii();
//    		} else if (rPr.getRFonts().getAsciiTheme()!=null 
//    					&& themePart != null) {
//    			log.debug("Encountered rFonts/AsciiTheme: " + rPr.getRFonts().getAsciiTheme() );
//    			
//				org.docx4j.dml.Theme theme = (org.docx4j.dml.Theme)themePart.getJaxbElement();
//				org.docx4j.dml.BaseStyles.FontScheme fontScheme = themePart.getFontScheme();
//				if (rPr.getRFonts().getAsciiTheme().equals(org.docx4j.wml.STTheme.MINOR_H_ANSI)) {
//					if (fontScheme != null && fontScheme.getMinorFont().getLatin() != null) {
//						fontScheme = theme.getThemeElements().getFontScheme();
//						org.docx4j.dml.TextFont textFont = fontScheme.getMinorFont().getLatin();
//						log.info("minorFont/latin font is " + textFont.getTypeface());
//						return (textFont.getTypeface());
//					} else {
//						// No minorFont/latin in theme part - default to Calibri
//						log.info("No minorFont/latin in theme part - default to Calibri");
//						return ("Calibri");
//					}
//				} else if (rPr.getRFonts().getAsciiTheme().equals(org.docx4j.wml.STTheme.MAJOR_H_ANSI)) {
//					if (fontScheme != null && fontScheme.getMajorFont().getLatin() != null) {
//						fontScheme = theme.getThemeElements().getFontScheme();
//						org.docx4j.dml.TextFont textFont = fontScheme.getMajorFont().getLatin();
//						log.debug("majorFont/latin font is " + textFont.getTypeface());
//						return (textFont.getTypeface());
//					} else {
//						// No minorFont/latin in theme part - default to Cambria
//						log.info("No majorFont/latin in theme part - default to Cambria");
//						return ("Cambria");
//					}
//				} else {
//					log.error("Don't know how to handle: "
//							+ rPr.getRFonts().getAsciiTheme());
//				}
//    		}
//    	}
//        		
//        // 2 if not, what does this styles basedOn style say? (recursive)
//    	
//    	if (style.getBasedOn()!=null && style.getBasedOn().getVal()!=null) {
//        	String basedOnStyleName = style.getBasedOn().getVal();    		
//    		//log.debug("recursing into basedOn:" + basedOnStyleName);
//            org.docx4j.wml.Style candidateStyle = (org.docx4j.wml.Style)stylesDefined.get(basedOnStyleName);
//            if (candidateStyle != null && candidateStyle.getStyleId().equals(basedOnStyleName)) {
//            	return getFontnameFromStyle(stylesDefined, themePart, candidateStyle);
//            }
//    	     // If we get here the style is missing!
//     		log.error("couldn't find basedOn:" + basedOnStyleName);    	     
//    	     return null;
//    	} else {
//    		//log.debug("No basedOn set for: " + style.getStyleId() );
//    		return null;
//    	}
//    	
//    }
//
	
}
